import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

// class VirtualMachine, representing a virtual machine
public class VirtualMachine implements Runnable {
	
	// for multithreading during boot process
	Thread t;
	// keeping track
	public int identifier;
	// ip address
	public String ip;
	// every VM has its own (das4 local) runtime environment
	public Runtime runtime;
	// machine template (default centos)
	private String machineTemplate;
	// debug output
	private boolean debug;
	// check if the machine is busy
	private boolean busy;
	// status of the machine (booting, running, failure, etc)
	private String status;
	// ID of the java thread the VM is in
	private String threadId;
	// check wether SSH is possible (after about 120 seconds)
	private long bootTime;
	
	// Contructor
	public VirtualMachine(int identifier) throws IOException {
		
		this.identifier = identifier;
		this.threadId = ("VM_"+Math.random()).substring(5, 10);
		
		// construct new java thread BEFORE booting
		this.t = new Thread(this, this.threadId);
		this.t.start();
	}
	
	// local function for executing commands on the DAS4 bash shell
	private String executeCommand(String command) throws IOException {
		this.busy = true;
		// use the shell to execute the command
		String[] cmd = {"/bin/sh", "-c", command};
		Process pr = this.runtime.exec(cmd);
		try {
			pr.waitFor() ;
		} catch (InterruptedException e) {
			System.out.println("InterruptedException occured while executing a shell command on this virtual machine...\nThe command was:\n****");
			System.out.print(command+"\n****\n");
			System.out.println("Terminating...");
			this.shutDown();
			e.printStackTrace();
		}
		// get output from terminal
		BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream())) ;
		String line;
		String output = "";
		while ((line = buf.readLine()) != null) {
			output+=(line) ;
		}
		this.busy = false;
		return output;
	}
	
	
	// function to push a file to the virtual machine
	public void pushFileToVM(String file) throws IOException, FileNotFoundException {
		this.executeCommand("scp "+file+" root@"+this.ip+":/var/www/"+file);
	}
	
	// function for the wordcount (actually, vowels count because that takes longer)
	public int countWords(String file) throws IOException, FileNotFoundException {
		// push file to server
		this.executeCommand("scp "+file+" root@"+this.ip+":/var/www/"+file);
		// grep all the vowels from the file
		this.executeCommand("ssh root@"+this.ip+" \"grep -o '[aeuio]' /var/www/"+file+" | wc -l > /var/www/output.txt\"");
		// download output file
		this.executeCommand("scp root@"+this.ip+":/var/www/output.txt"+" "+file);
		// read and return output file (number of vowels)
		if(Integer.parseInt(this.executeCommand("stat -c%s "+file)) > 10) System.out.println("Something went wrong with machine "+this.identifier);
		
		String output = new Scanner(new File(file)).useDelimiter("\\Z").next();
		int words = Integer.parseInt(output);
		return words;
	}
	
	// Run script on VM
	public String run(String script) throws IOException, FileNotFoundException {
		// copy script to VM
		this.executeCommand("scp "+script+" root@"+this.ip+":/var/www/"+script);
		// execute script
		this.executeCommand("ssh root@"+this.ip+" 'bash -s' < "+script);
		// download output
		this.executeCommand("scp root@"+this.ip+":/var/www/output.txt"+" output.txt");
		// return output
		String output = new Scanner(new File("output.txt")).useDelimiter("\\Z").next();
		return output;
	}
	
	// shutdown the virtual machine
	public void shutDown() throws IOException {
		this.executeCommand("onevm shutdown "+this.identifier);
	}
	
	// delete the virtual machine from the list (for failures)
	public void delete() throws IOException {
		this.executeCommand("onevm delete "+this.identifier);
	}
	
	// boot the machine. loops in the thread till booted
	private void boot() throws IOException, InterruptedException {
		// wait till status is running
		String currentStatus = this.status();
		while(currentStatus.equals("prol") || currentStatus.equals("boot") || currentStatus.equals("pend")) {
			Thread.sleep(1000);
			currentStatus = this.status();
		}
		// might went wrong
		if(!this.status().equals("runn")) {
			this.delete();
		} else {
			this.busy = false;
		}
	}
	
	// check the status of the virtual machine
	public String status() throws IOException {
		String temp = this.executeCommand("onevm list -l ID,STAT | grep "+this.identifier);
		if(temp.length() > 3) {
			this.status = temp.substring(temp.length()-4);
		}
		return this.status;
	}
	
	// for debug output
	public String toString() {
		return this.identifier+" | "+this.ip+" | "+this.status+"    | "+this.threadId+" ";
	}
	
	// debug output
	public void debug(String s) {
		if(this.debug) System.out.println(s);
	}
	
	// check if the machine is busy
	public boolean isBusy() throws IOException {
		return this.busy;
	}
	
	// check if it is possible to ssh to the virtual machine (roughly about 130 seconds)
	public boolean sshReady() {
		return System.currentTimeMillis() - this.bootTime > 130000;
	}

	// function executed when the THREAD is started, creating the machine in the constructor
	@Override
	public void run() {
		// defaults
		this.debug = false;
		this.machineTemplate = "centos.one";
		this.busy = true;
		
		// every VM has its own runtime environment
		this.runtime = Runtime.getRuntime();
		String shellOutput = "";
		
		// in case of a new identifier, a new virtual machine must be created
		if (this.identifier==0) {
			try {
				// keep track of boottime for SSH and create new machine
				this.bootTime = System.currentTimeMillis();
				shellOutput = this.executeCommand("onevm create "+this.machineTemplate);
			} catch (IOException e) {
				System.out.println("Something went wrong during the creation of the machine...");
				e.printStackTrace();
			}
			this.identifier = Integer.parseInt(shellOutput.substring(4));
		}
		
		// fetch the IP adress
		try {
			shellOutput = this.executeCommand("onevm show "+this.identifier+" | grep IP= | sed 's/.*IP=\"*\\([^\"]*\\)\"*,/\\1/'");
		} catch (IOException e) {
			System.out.println("Something went wrong during the creation of the machine...");
			e.printStackTrace();
		}
		this.ip = shellOutput;
		
		// if the machine is a new virtual machine, it has to be booted
		if(this.status != "runn") {
			try {
				// boot the virtual machine
				this.boot();
			} catch (IOException e) {
				System.out.println("Something went wrong while booting the machine");
				e.printStackTrace();
			} catch (InterruptedException e) {
				System.out.println("Something went wrong while booting the machine");
				e.printStackTrace();
			}
		} else {
			// if it is NOT new, SSH can be used directly (boottime = 1970, dirty but nice!)
			this.bootTime = 0;
		}
	}
}
